// 定义 100 * 100 行row  列col
const row = 100, col = 100;
let map = Array(row * col).fill(0)

// 是否动态显示 有动画效果
let tFlag = true;
let tRate = 0.1; // 阻塞延迟的概率 0-1
let logFlag = true; // 是否打印进行日志

let createFoodFlag = false; // 生成食物中 不进行移动

let gameRun = true; // 游戏进行中

// 获取 container 元素对象
let containerDom; // 容器
let snakeLenDom; // 展示 蛇长度
let pathLenDom; // 展示路径数
let textDom;

// 蛇 数组
const snake = [
  [3,3], // x y
  [2,3],
  [1,3],
]

// 记录 食物生成阶段蛇的数组 用来排错 模拟
let snakeFoodTmep = []

// 食物坐标
const food = [3,10];

// 路径坐标 蛇头到食物的数组
let path = [];
// 路径坐标 蛇头到蛇尾的数组
let headFootPath = [];
let headFootPathLen = 0;

// 上次 蛇的数组 用来清除
let lastSnake = []


const bgKeys = {
  'default': '#2d2f42',
  'head': 'aqua',
  'body': '#fff',
  'path': 'red',
  'food': 'yellow'
}

const UP = 'up'
const DOWN = 'down'
const LEFT = 'left'
const RIGHT = 'right'

// 方向
let direction = DOWN;

let cache = true;

// game 进行中
let actionGameRun = false;

window.onload =async () => {
  containerDom = document.getElementById('container');
  snakeLenDom = document.getElementById('snakeLen');
  pathLenDom = document.getElementById('pathLen');
  textDom = document.getElementById('text');
  
  await init()
  await createSnake(cache)
  // 食物创建
  await createFood(cache)

  // await game()
  // setInterval(async () => gameRun && !createFoodFlag && await game(), 5)

  // setTimeout(() => game(),500)
}

// 生成 蛇的路径组
async function createSnake(cache = false) {
  tFlag = false;
  logFlag = false;

  const snakeStr = cache ? localStorage.getItem('snake') : ''
  if (snakeStr) {
    const v = JSON.parse(snakeStr)
    snake.splice(0,snake.length)
    for(const i in v) {
      const k = v[i][1] * row + v[i][0];
      map[k] = 1;
      snake.push(v[i])
      containerDom.children[k].style.backgroundColor = bgKeys['body']
    }
    containerDom.children[snake[0][1] * row + snake[0][0]].style.backgroundColor = bgKeys['head']
    return true;
  }

  // 随机取两个点 因为此处还没有身体 所以不需要考虑 重叠的情况 只要这两个点 不同样即可
  const head = [Math.floor(80 + Math.random() * (row - 80)),Math.floor(80 + Math.random() * (row - 80))]
  const headIndex = head[1] * row + head[0]

  foot = [Math.floor(Math.random() * row / 4),Math.floor(Math.random() * col / 4)]
  const footIndex = foot[1] * row + foot[0]
  const headFootIndexs = [headIndex, footIndex]
  
  // 随机画线 添加 路径多变
  const ball = {}
  const lineLen = 25;
  for(let i = 0; i < lineLen ;i++) {
    const s = [
      Math.floor(Math.random() * row),
      Math.floor(Math.random() * col)
    ];
    const sIndex = s[1] * row + s[0]

    const e = [
      Math.floor(Math.random() * row),
      Math.floor(Math.random() * col)
    ];
    const eIndex = e[1] * row + e[0]

    if ( 
      headFootIndexs.indexOf(sIndex) != -1
      ||
      headFootIndexs.indexOf(eIndex) != -1 
    ) continue;

    // 画线 分为 横 或 竖线 以及长度
    
    // findPath 
    const pathTemp = await findPath(Array(row * col).fill(0), s,e,false)
    if (!pathTemp && !pathTemp.length) continue;
    map[sIndex] = 1;
    map[eIndex] = 1;
    containerDom.children[sIndex].style.backgroundColor = bgKeys['default']
    containerDom.children[eIndex].style.backgroundColor = bgKeys['default']
    for(const j in pathTemp) {
      const k = pathTemp[j][1] * row + pathTemp[j][0];
      map[k] = 1;
      ball[pathTemp[j][0] + '_' + pathTemp[j][1]] = 1;
      containerDom.children[k].style.backgroundColor = bgKeys['default']
    }
  }
  
  let pathTemp = false;
  do {
    pathTemp = await findPath(map, head,foot)
    if (pathTemp && pathTemp.length) break;
    head[0] = Math.floor(Math.random() * row)
    head[1] = Math.floor(Math.random() * col)
  
    foot[0] = Math.floor(Math.random() * row)
    foot[1] = Math.floor(Math.random() * col)
  } while(true)
  map = Array(row * col).fill(0)
  snake.splice(0,snake.length)
  for (let i in pathTemp) {
    const k = pathTemp[i][1] * row + pathTemp[i][0];
    map[k] = 1;
    snake.push(pathTemp[i])
    let color = 'body' 
    containerDom.children[k].style.backgroundColor = bgKeys[color]
  }
  
  containerDom.children[snake[0][1] * row + snake[0][0]].style.backgroundColor = bgKeys['head']
  tFlag = true;
  logFlag = true;
  console.log('snake len', snake.length)
  snakeLenDom.innerText = snake.length
}

// 游戏开始
async function game() {
  if (actionGameRun) return ;
  actionGameRun = true;
  // 清除点
  if (lastSnake.length) {
    for(let i = 0;i < lastSnake.length;i++) {
      [x,y] = lastSnake[i]
      if (!containerDom.children[y * row + x]) continue;
      containerDom.children[y * row + x].style.backgroundColor = bgKeys['default'];
      map[y * row + x] = 0; 
    }
  }

  // 绘制蛇
  for(let i = 0;i< snake.length;i++) {
    [x,y] = snake[i]
    const bgKey = i == 0 ? 'head' : 'body';
    if (!containerDom.children[y * row + x]) continue;
    containerDom.children[y * row + x].style.backgroundColor = bgKeys[bgKey];
    map[y * row + x] = 1;
  }

  // 记录上一次 需要清除
  lastSnake = JSON.parse(JSON.stringify(snake))

  // 自动计算方向
  await autoDirection()

  // 移动
  await move()

  actionGameRun = false;
}

// 计算 贪吃蛇的方向
async function autoDirection() {
  // 计算方向
  async function actionDirection(movePoint) {
    // 蛇头的下一个点
    if (snake[0][0] != movePoint[0]) {
      if (snake[0][0] > movePoint[0]) direction = LEFT;
      else if (snake[0][0] < movePoint[0]) direction = RIGHT;
    } else if (snake[0][1] != movePoint[1]) {
      if (snake[0][1] > movePoint[1]) direction = UP;
      else if (snake[0][1] < movePoint[1]) direction = DOWN;
    }
  }

  // 如果有 食物路径 优先级
  path.pop()
  const pathLen = path.length;
  if (pathLen) {
    return actionDirection(path[pathLen-1])
    if (snake.length < 250) {
      return actionDirection(path[pathLen-1])
    }

    // 从食物点 到 蛇尾 能否吃到 吃不到说明危险
    const snakeFootIndex = snake[snake.length - 1][1] * row + snake[snake.length - 1][0];
    map[snakeFootIndex] = 0;
    const foodFootPath = await findPath(map, food, snake[snake.length - 1], false, 'rgba(253, 164, 0, .5)')
    map[snakeFootIndex] = 1;
    if (foodFootPath.length) return actionDirection(path[pathLen-1])
    else if(!headFootPath.length) {
      // await findSnakeFood();
      console.log("食物点到蛇尾：", foodFootPath.length, foodFootPath.toString())
    }
    return actionDirection(path[pathLen-1])
  }

  const foodPathLen = headFootPath.length;
  // 如果没有  那么走蛇尾路径 
    // 走完蛇尾路径后 重新调用食物路径 还是没有 继续 寻找蛇尾路径
  if (foodPathLen) {
    actionDirection(headFootPath.pop())
    // 说明蛇尾 已走完 先进行寻找 食物 如果找不到就再进行找蛇尾
    if (headFootPath.length <= 0) {
      await findPath(map, snake[0], food)
      console.log('食物长度为：',path.length)
      if (path.length <= 0) {
        console.log("没找到食物路径，继续寻找蛇尾")
        await findSnakeFood();
      }
    }
    return ;
  }  
}

// 生成食物
async function createFood(cache = false) {
  createFoodFlag = true;
  snakeFoodTmep = JSON.parse(JSON.stringify(snake))
  console.log(' 生成食物中....')
  const snakeKeys = {}
  snake.forEach(item => snakeKeys[item[1] * row + item[0]] = true)
  do {
    const foodStr = cache ? localStorage.getItem('food') : ''
    if (foodStr) {
      const [foodX, foodY] = JSON.parse(foodStr)
      food[0] = foodX;
      food[1] = foodY;
    } else {
      food[0] = Math.floor(Math.random() * row);
      food[1] = Math.floor(Math.random() * col);
    }

    // 食物不能在蛇身上
    if (!snakeKeys[food[1] * row + food[0]]) break;
  } while(true)
  const key = food[1] * row + food[0];
  containerDom.children[key].style.backgroundColor = bgKeys['food']
  map[key] = 0;
  console.log(' 食物点为：', food)

  // 自动算出方向
  console.log(' 自动寻路中....')
  await findPath(map, snake[0], food)
  if (path.length == 1) {
    path.pop()
  }
  console.log(" 寻路结束，需走：", path.length + "步")
  // 没有找到 那就寻找到蛇尾的路径
  if (!path.length) {
    gameRun = false;
    console.log('没有路径 结束：', path)
    localStorage.setItem('map', JSON.stringify(map))
    for(const i in map) {
      if(!map[i]) continue;
      containerDom.children[i].style.backgroundColor = "#b078ef"
    }
    await findSnakeFood();
  }
  createFoodFlag = false;
}

// 寻找蛇尾的路线
async function findSnakeFood() {
  const snakeFood = snake[snake.length - 1];
  map[snakeFood[1] * row + snakeFood[0]] = 0;
  const headFootPathTemp = await findPath(map, snake[0], snakeFood, false)
  map[snakeFood[1] * row + snakeFood[0]] = 1;
  if (headFootPathTemp) {
    headFootPathTemp.pop()
    headFootPath = headFootPathTemp;
    headFootPathLen = headFootPathTemp.length;
  }
  console.log('寻找蛇尾的路线:', headFootPathLen)
}

// 设置路径数
function setPathLen(len = 0) {
  pathLenDom.innerText = len;
}

// 移动方向
async function move() {
  // 后面的点 往前移动
  for(let i = snake.length - 1;i>0;i--) {
    snake[i] = snake[i -1]
  }
  
  // 弹出头部
  const [x,y] = snake.shift()

  // 移动后 塞进头部
  if (direction == RIGHT) 
    snake.unshift([x + 1,y]);
  else if (direction == LEFT)
    snake.unshift([x - 1,y]); 
  else if (direction == UP)
    snake.unshift([x,y - 1]); 
  else if (direction == DOWN)
    snake.unshift([x,y + 1]);
  
  if (snake[0][0] < 0 || snake[0][0] > row || snake[0][1] < 0 || snake[0][1] > col) { // 过界 死亡
    gameRun = false;
    localStorage.setItem('snake', JSON.stringify(snakeFoodTmep))
    localStorage.setItem('food', JSON.stringify(food))
    localStorage.setItem('direction', direction)
    console.log('direction game over', direction)
  } else if (snake[0][0] == food[0] && snake[0][1] == food[1]) {  // 吃到了食物
    snake.push(snake[snake.length-1])
    // 标记蛇头颜色
    containerDom.children[snake[0][1] * row + snake[0][0]].style.backgroundColor = bgKeys['head']
    console.log("*****************")
    console.log('吃到食物了 蛇长度：', snake.length)
    snakeLenDom.innerText = snake.length;
    // 重新创建食物
    await createFood()
  }
}

// 初始化函数 
async function init() {
  // 画地图
  const div = document.createElement('div');
  div.className = "cell"
  map.forEach(() => {
    const d = div.cloneNode()
    containerDom.append(d);
  })

  // 按下
  const downKeys = {
    ArrowDown: DOWN, 
    ArrowUp: UP,
    ArrowLeft: LEFT,
    ArrowRight: RIGHT
  }
  document.addEventListener('keydown',async function(e){
    if (downKeys[e.key]) {
      direction = downKeys[e.key];
    } else if (e.key == 'Enter') {
      gameRun = !gameRun;
      textDom.innerText = gameRun ? '' : '暂停中';
    }
  })
}

/**
 * ------------------ START 寻路算法开始 --------------------------
 */

// 等待函数
function sleep(t) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve,t);
  })
}

// 获取格与格之间的距离
function distance(point,end) {
  // 使用三角形 x^2 + y ^ 2 = z ^ 2 计算距离 当前点和终点的距离 距离越小 则表示最近
  // console.log(point, end, (point[0] - end[0]) ** 2 + (point[1] - end[1]) ** 2)
  return (point[0] - end[0]) ** 2 + (point[1] - end[1]) ** 2;
}


// 寻路方法
async function findPath(map, start ,end, setPathFlag = true, color = '') {
  // 起点颜色
  container.children[start[1] * col + start[0]].style.backgroundColor = 'red'
  // 终点颜色
  container.children[end[1] * row + end[0]].style.backgroundColor = 'yellow'

  // 用于记录 上一个路径 新的数组 防止 影响到源数据
  const table = JSON.parse(JSON.stringify(map))
  // 初始化队列 包含了 起点
  // var queue = [start];
  let queue = new Sorted([start], (a,b) => {
    const x1Arec = distance(a,end);
    const x2Arec = distance(b,end);
    // console.log(`   x1:${a[0]},y1:${a[1]} x2:${b[0]},y2:${b[1]} => x1Arec:${x1Arec} - x2Arec:${x2Arec} = ${x1Arec - x2Arec}`)
    return x1Arec - x2Arec;
  });

  let i = 0;
  
  // 入队 
  async function insert(x,y,pre, t = 0) {
    if (x == start[0] && y == start[1]) return ;

    // 超出边界 直接停止
    if (x < 0 || x >= row || y < 0 || y >= col) return ;
    // 遇到地图的墙  停止 墙是 0- col 列行的数组 所以 y * row行 + x
    if (table[y * row + x]) return ;

    // 加入30  毫秒的停顿
    if (t) await sleep(1);

    // 给搜索到的路径格子加个背景颜色
    // container.children[y * row + x].style.backgroundColor = 'DARKSLATEBLUE';

    // 标记走过的格子的值，标记为上一个格子的 x,y位置
    table[y * row + x] = pre;
    queue.give([x,y]);
    // queue.push([x,y])
    i++;
  }

  
  // 循环格子4边
  while (queue.length) {
    // 给一个距离终点最近的点
    let [x, y] = queue.take();
    // if(logFlag) console.log(` 当前最近的点：${x}, ${y}}，长度：${queue.length}`)
    // let [x,y] = queue.shift()

    // 终点可以返回
    if ( x === end[0] && y === end[1]) {    
      // 起点颜色
      container.children[start[1] * row + start[0]].style.backgroundColor = 'red'

      // 进行逆推 退回来就可以得到最佳路线
      let pathTemp = [[x,y]];
      let pathX = x;
      let pathY = y;
      while (pathX != start[0] || pathY != start[1]) {
        const index = pathY * row + pathX;
        if (table[index] && typeof table[index] == 'object') {
          pathX = table[index][0]
          pathY = table[index][1]
          pathTemp.push([pathX,pathY]);
          container.children[index].style.backgroundColor = color ? color : 'rgba(81, 125, 138, .5)'
        }
      }

      // 终点颜色
      container.children[end[1] * row + end[0]].style.backgroundColor = 'yellow'

      if(logFlag) console.log('路径长度：', pathTemp.length, '总迭代次数：'+i)
      setPathLen(pathTemp.length)
      
      if (setPathFlag) {
        path = pathTemp
      }

      return pathTemp;
    }

    // 方向 上下左右 顺时针  应该是 左 上 此处可以加优化 判断哪个方向
    await insert(x - 1, y, [x,y], t = tFlag && Math.random() < tRate ? 1 : 0) // 左
    await insert(x, y - 1, [x,y], t = tFlag && Math.random() < tRate ? 1 : 0) // 上
    await insert(x + 1, y, [x,y], t = tFlag && Math.random() < tRate ? 1 : 0) // 右
    await insert(x, y + 1, [x,y], t = tFlag && Math.random() < tRate ? 1 : 0) // 下
  }
  return false;
}
/**
 * ------------------ END 寻路算法结束 ----------------------------
 */

